/*
Copyright (C) 2009-2010 Xanadu Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2
of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "../provider/DataProvider.h"
#include "../tools/PacketCreator.h"
#include "Mob.h"
#include "../net/world/Party.h"
#include "Player.h"
#include "MapPlayers.h"
#include "Map.h"
#include "../net/world/Parties.h"
#include "../net/world/PartyMembers.h"
#include "Channel.h"
#include "../net/world/World.h"
#include "../net/world/Worlds.h"

class ComparePlayersDist : public std::binary_function<Player*, Player*, bool> {
	Position pos;
public:
	bool operator()(Player* x, Player* y) const {
		return Tools::getDistance((Position&)pos, x->getPosition()) < Tools::getDistance((Position&)pos, y->getPosition());
	}
	ComparePlayersDist(Position& pos) {
		this->pos = pos;		
	}
};

int round(double n) {
	if(((int)n*10)%10 > 5)
		return (int)(n+1);
	else
		return (int)n;
}

int Mob::getMaxMp() {
	return DataProvider::getInstance()->getMobMaxMp(mobid);
}

int Mob::getMaxHp() {
	return DataProvider::getInstance()->getMobMaxHp(mobid);
}

bool Mob::isBoss() {
	return DataProvider::getInstance()->isMobBoss(mobid);
}

void Mob::takeDamage(Player* player, int damage) {
	hp -= damage;
	int ndamage = damage;
	if(hp < 0) {
		ndamage += hp;
		hp = 0;
	}
	if(player != 0) {
		if(playersa.find(player->getID()) == playersa.end())
			playersa[player->getID()] = ndamage;
		else
			playersa[player->getID()] += ndamage;
	}
	char per = (char)(hp * 100.0 / getMaxHp());
	if(isBoss()) {
		char color = DataProvider::getInstance()->getMobTagColor(mobid);
		if(color != 0) {
			map->send(PacketCreator().showBossHp(mobid, hp, getMaxHp(), per, color, DataProvider::getInstance()->getMobTagBgColor(mobid)));
		}
	}
	else if(DataProvider::getInstance()->getMobDamagedByMob(mobid)) {
		map->send(PacketCreator().damageByMob(id, damage, hp, getMaxHp()));
	}
	else {
		for (hash_map<int,int>::iterator iter = playersa.begin(); iter != playersa.end(); iter++) {
			Player* p = map->getPlayers()->getPlayerByID(iter->first);
			if(p != NULL && p->getMap() == map) {
				p->send(PacketCreator().showHp(id, per));
			}
		}
	}
}

void Mob::giveExp() {
	hash_map <int, int> maxdamage;
	hash_map <int, Player*> maxplayer;
	hash_map <int, int> damages;
	for (hash_map<int,int>::iterator iter = playersa.begin(); iter != playersa.end(); iter++) {
		Player* p = map->getPlayers()->getPlayerByID(iter->first);
		if(p != 0) {
			if(p->getParty() == 0) {
				int Hpk = iter->second;
				double fexp = Hpk * Worlds::getInstance()->getExpRate() * 100.0 * DataProvider::getInstance()->getMobExp(mobid) / 100.0 / getMaxHp();
				int exp = round(fexp);
				if(exp != 0) {
					p->send(PacketCreator().gainExp(exp, false, true));
					p->setExp(p->getExp() + exp);
				}
			}
			else{
				int pid = p->getParty()->getID();
				if(maxdamage.find(pid) == maxdamage.end() || iter->second > maxdamage[pid]) {
					if(maxdamage.find(pid) == maxdamage.end()) {
						damages[pid] = 0;
					}
					maxdamage[pid] = iter->second;
					maxplayer[pid] = p;
					damages[pid] += iter->second;
				}
			}
		}
	}
	for (hash_map<int,int>::iterator d = damages.begin(); d != damages.end(); d++) {
		Party* party = map->getChannel()->getWorld()->getParties()->getParty(d->first);
		if(party == 0)
			continue;
		int Hpk = d->second;
		double fexp = Hpk * Worlds::getInstance()->getExpRate() * 100.0 * DataProvider::getInstance()->getMobExp(mobid) / 100.0 / getMaxHp();
		int baseexp = round(fexp);
		vector <Player*> players;
		hash_map <int, Player*>* ps = party->getMembers()->getPlayers();
		int count = 0;
		for(hash_map<int,Player*>::iterator iter = ps->begin(); iter != ps->end(); iter++) {
			if(iter->second->getMap() == map) {
				count++;
				if(playersa.find(iter->second->getID()) != playersa.end() || iter->second->getLevel() >= maxplayer[party->getID()]->getLevel() - 5 || iter->second->getLevel() >= DataProvider::getInstance()->getMobLevel(mobid) - 5)
					players.push_back(iter->second);
			}
		}
		if(count == 1) {
			maxplayer[party->getID()]->send(PacketCreator().gainExp(baseexp, false, true));
			maxplayer[party->getID()]->setExp(maxplayer[party->getID()]->getExp() + baseexp);
			continue;
		}
		int levels = 0;
		for(int i = 0; i < (int)players.size(); i++) {
			levels += players[i]->getLevel();
		}
		int bonus = (int)players.size() * 5;
		for(int i = 0; i < (int)players.size(); i++) {
			int exp = round(baseexp * 0.8 * players[i]->getLevel() / levels);
			if(players[i] == maxplayer[party->getID()]) exp += round(baseexp * 0.2);
			if(exp > 0 && players[i]->getHp() > 0) {
				exp = (int)(exp * (1 + bonus / 100.0));
				players[i]->setExp(players[i]->getExp() + exp);
				players[i]->send(PacketCreator().gainExp(exp, false, players[i] == maxplayer[party->getID()], bonus));
			}
		}
	}
}

int getDistance(Position& pos1, Position& pos2) {
	return (int)(pow((double)(pos1.x+pos2.x), 2)+pow((double)(pos1.y+pos2.y), 1));
}

void Mob::findControl(bool spawn) {
	vector <Player*> players = vector <Player*>(*map->getPlayers()->getPlayersList());
	if(players.empty()) {
		control = 0;
		return;
	}
	Player* player = *min_element<vector <Player*>::iterator, ComparePlayersDist>(players.begin(), players.end(), ComparePlayersDist(pos));
	setControl(*players.begin(), spawn && DataProvider::getInstance()->getMobFirstAttack(mobid), false);
}

void Mob::setControl(Player* control, bool agrs, bool switchc) {
	if(getControl() != 0 && switchc)
		this->control->send(PacketCreator().endControlMob(id));
	this->control = control;
	if(control != NULL)
		control->send(PacketCreator().controlMob(this, MOB_SHOW, agrs));
	aggressive = agrs;
}

int Mob::getKillerId() {
	int md = 0;
	int mdid = 0;
	for (hash_map<int,int>::iterator iter = playersa.begin(); iter != playersa.end(); iter++) {
		if(iter->second > md) {
			md = iter->second;
			mdid = iter->first;
		}
	}
	return mdid;
}

Player* Mob::getControl() {
	if(control != 0 && !map->getPlayers()->find(control))
		control = 0;
	return control;
}

void Mob::setDropTimer(Timer* timer)
{
	dropTimer = timer;
}

Timer* Mob::getDropTimer()
{
	return dropTimer;
}